คู่มือฉบับสมบูรณ์เกี่ยวกับการทำความเข้าใจและการนำ TypeScript middleware ไปใช้ในแอปพลิเคชัน Express.js สำรวจรูปแบบประเภทขั้นสูงเพื่อโค้ดที่แข็งแกร่งและดูแลรักษาง่าย
TypeScript Middleware: การเรียนรู้รูปแบบประเภท Express Middleware
Express.js ซึ่งเป็นเฟรมเวิร์กแอปพลิเคชันเว็บ Node.js ที่เรียบง่ายและยืดหยุ่น ช่วยให้นักพัฒนาสามารถสร้าง API และแอปพลิเคชันเว็บที่แข็งแกร่งและปรับขนาดได้ TypeScript ช่วยเสริมประสิทธิภาพของ Express โดยการเพิ่มการพิมพ์แบบคงที่ ปรับปรุงความสามารถในการบำรุงรักษาโค้ด และตรวจจับข้อผิดพลาดตั้งแต่เนิ่นๆ ฟังก์ชัน Middleware เป็นหัวใจสำคัญของ Express ช่วยให้คุณสกัดกั้นและประมวลผลคำขอก่อนที่จะไปถึงตัวจัดการเส้นทางของคุณ บทความนี้จะสำรวจรูปแบบประเภท TypeScript ขั้นสูงสำหรับการกำหนดและการใช้ Express middleware ซึ่งช่วยเพิ่มความปลอดภัยของชนิดข้อมูลและความชัดเจนของโค้ด
ทำความเข้าใจ Express Middleware
ฟังก์ชัน Middleware คือฟังก์ชันที่มีสิทธิ์เข้าถึงวัตถุคำขอ (req), วัตถุการตอบสนอง (res) และฟังก์ชัน middleware ถัดไปในวงจรการตอบสนองคำขอของแอปพลิเคชัน ฟังก์ชัน Middleware สามารถทำงานต่อไปนี้ได้:
- ดำเนินการโค้ดใดๆ
- ทำการเปลี่ยนแปลงวัตถุคำขอและการตอบสนอง
- สิ้นสุดวงจรการตอบสนองคำขอ
- เรียกใช้ฟังก์ชัน middleware ถัดไปในสแต็ก
ฟังก์ชัน Middleware จะถูกดำเนินการตามลำดับเมื่อมีการเพิ่มไปยังแอปพลิเคชัน Express กรณีการใช้งานทั่วไปสำหรับ middleware ได้แก่:
- การบันทึกคำขอ
- การตรวจสอบสิทธิ์ผู้ใช้
- การอนุญาตการเข้าถึงทรัพยากร
- การตรวจสอบความถูกต้องของข้อมูลคำขอ
- การจัดการข้อผิดพลาด
Basic TypeScript Middleware
ในแอปพลิเคชัน TypeScript Express พื้นฐาน ฟังก์ชัน middleware อาจมีลักษณะดังนี้:
import { Request, Response, NextFunction } from 'express';
function loggerMiddleware(req: Request, res: Response, next: NextFunction) {
console.log(`Request: ${req.method} ${req.url}`);
next();
}
export default loggerMiddleware;
middleware ง่ายๆ นี้จะบันทึกวิธีการร้องขอและ URL ไปยังคอนโซล มาทำลายคำอธิบายประเภทกันเถอะ:
Request: แสดงถึงวัตถุคำขอ ExpressResponse: แสดงถึงวัตถุการตอบสนอง ExpressNextFunction: ฟังก์ชันที่เมื่อถูกเรียกใช้ จะดำเนินการ middleware ถัดไปในสแต็ก
คุณสามารถใช้ middleware นี้ในแอปพลิเคชัน Express ของคุณได้ดังนี้:
import express from 'express';
import loggerMiddleware from './middleware/loggerMiddleware';
const app = express();
const port = 3000;
app.use(loggerMiddleware);
app.get('/', (req, res) => {
res.send('Hello, world!');
});
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
รูปแบบประเภทขั้นสูงสำหรับ Middleware
ในขณะที่ตัวอย่าง middleware พื้นฐานใช้งานได้จริง แต่มันขาดความยืดหยุ่นและความปลอดภัยของชนิดข้อมูลสำหรับสถานการณ์ที่ซับซ้อนมากขึ้น มาสำรวจรูปแบบประเภทขั้นสูงที่ช่วยเสริมการพัฒนา middleware ด้วย TypeScript กัน
1. Custom Request/Response Types
บ่อยครั้ง คุณจะต้องขยายวัตถุ Request หรือ Response ด้วยคุณสมบัติที่กำหนดเอง ตัวอย่างเช่น หลังจากตรวจสอบสิทธิ์ คุณอาจต้องการเพิ่มคุณสมบัติ user ลงในวัตถุ Request TypeScript ช่วยให้คุณสามารถเพิ่มประเภทที่มีอยู่ได้โดยใช้การผสานการประกาศ
// src/types/express/index.d.ts
import { Request as ExpressRequest } from 'express';
declare global {
namespace Express {
interface Request {
user?: {
id: string;
email: string;
// ... other user properties
};
}
}
}
export {}; // This is needed to make the file a module
ในตัวอย่างนี้ เรากำลังเพิ่มส่วนต่อประสาน Express.Request เพื่อรวมคุณสมบัติ user ที่เป็นตัวเลือก ตอนนี้ ใน middleware การตรวจสอบสิทธิ์ของคุณ คุณสามารถเติมคุณสมบัตินี้ได้:
import { Request, Response, NextFunction } from 'express';
function authenticationMiddleware(req: Request, res: Response, next: NextFunction) {
// Simulate authentication logic
const userId = req.headers['x-user-id'] as string; // Or fetch from a token, etc.
if (userId) {
// In a real application, you would fetch the user from a database
req.user = {
id: userId,
email: `user${userId}@example.com`
};
next();
} else {
res.status(401).send('Unauthorized');
}
}
export default authenticationMiddleware;
และในตัวจัดการเส้นทางของคุณ คุณสามารถเข้าถึงคุณสมบัติ req.user ได้อย่างปลอดภัย:
import express from 'express';
import authenticationMiddleware from './middleware/authenticationMiddleware';
const app = express();
const port = 3000;
app.use(authenticationMiddleware);
app.get('/profile', (req: Request, res: Response) => {
if (req.user) {
res.send(`Hello, ${req.user.email}! Your user ID is ${req.user.id}`);
} else {
// This should never happen if the middleware is working correctly
res.status(500).send('Internal Server Error');
}
});
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
2. Middleware Factories
Middleware factories คือฟังก์ชันที่ส่งคืนฟังก์ชัน middleware รูปแบบนี้มีประโยชน์เมื่อคุณต้องการกำหนดค่า middleware ด้วยตัวเลือกหรือการอ้างอิงเฉพาะ ตัวอย่างเช่น พิจารณา middleware การบันทึกที่บันทึกข้อความไปยังไฟล์เฉพาะ:
import { Request, Response, NextFunction } from 'express';
import fs from 'fs';
import path from 'path';
function createLoggingMiddleware(logFilePath: string) {
return (req: Request, res: Response, next: NextFunction) => {
const logMessage = `[${new Date().toISOString()}] Request: ${req.method} ${req.url}\n`;
fs.appendFile(logFilePath, logMessage, (err) => {
if (err) {
console.error('Error writing to log file:', err);
}
next();
});
};
}
export default createLoggingMiddleware;
คุณสามารถใช้ middleware factory นี้ได้ดังนี้:
import express from 'express';
import createLoggingMiddleware from './middleware/loggingMiddleware';
const app = express();
const port = 3000;
const logFilePath = path.join(__dirname, 'logs', 'requests.log');
app.use(createLoggingMiddleware(logFilePath));
app.get('/', (req, res) => {
res.send('Hello, world!');
});
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
3. Asynchronous Middleware
ฟังก์ชัน Middleware มักจะต้องดำเนินการแบบอะซิงโครนัส เช่น การสอบถามฐานข้อมูลหรือการเรียก API ในการจัดการการดำเนินการแบบอะซิงโครนัสอย่างถูกต้อง คุณต้องตรวจสอบให้แน่ใจว่าฟังก์ชัน next ถูกเรียกใช้หลังจากการดำเนินการแบบอะซิงโครนัสเสร็จสมบูรณ์ คุณสามารถทำได้โดยใช้ async/await หรือ Promises
import { Request, Response, NextFunction } from 'express';
async function asyncMiddleware(req: Request, res: Response, next: NextFunction) {
try {
// Simulate an asynchronous operation
await new Promise(resolve => setTimeout(resolve, 100));
console.log('Asynchronous operation completed');
next();
} catch (error) {
next(error); // Pass the error to the error handling middleware
}
}
export default asyncMiddleware;
สำคัญ: อย่าลืมจัดการข้อผิดพลาดภายใน middleware แบบอะซิงโครนัสของคุณและส่งต่อไปยัง middleware การจัดการข้อผิดพลาดโดยใช้ next(error) ซึ่งช่วยให้มั่นใจได้ว่าจะมีการจัดการและบันทึกข้อผิดพลาดอย่างถูกต้อง
4. Error Handling Middleware
Error handling middleware เป็น middleware ประเภทพิเศษที่จัดการข้อผิดพลาดที่เกิดขึ้นในระหว่างวงจรคำขอ-ตอบสนอง ฟังก์ชัน Error handling middleware มีอาร์กิวเมนต์สี่ตัว: err, req, res และ next
import { Request, Response, NextFunction } from 'express';
function errorHandler(err: any, req: Request, res: Response, next: NextFunction) {
console.error(err.stack);
res.status(500).send('Something went wrong!');
}
export default errorHandler;
คุณต้องลงทะเบียน error handling middleware หลังจาก middleware และตัวจัดการเส้นทางอื่นๆ ทั้งหมด Express ระบุ error-handling middleware โดยการมีอยู่ของอาร์กิวเมนต์ทั้งสี่
import express from 'express';
import asyncMiddleware from './middleware/asyncMiddleware';
import errorHandler from './middleware/errorHandler';
const app = express();
const port = 3000;
app.use(asyncMiddleware);
app.get('/', (req, res) => {
throw new Error('Simulated error!'); // Simulate an error
});
app.use(errorHandler); // Error handling middleware MUST be registered last
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
5. Request Validation Middleware
การตรวจสอบความถูกต้องของคำขอเป็นสิ่งสำคัญในการสร้าง API ที่ปลอดภัยและเชื่อถือได้ Middleware สามารถใช้เพื่อตรวจสอบข้อมูลคำขอขาเข้าและตรวจสอบให้แน่ใจว่าเป็นไปตามเกณฑ์บางอย่างก่อนที่จะไปถึงตัวจัดการเส้นทางของคุณ ไลบรารีเช่น joi หรือ express-validator สามารถใช้สำหรับการตรวจสอบความถูกต้องของคำขอ
นี่คือตัวอย่างโดยใช้ express-validator:
import { Request, Response, NextFunction } from 'express';
import { body, validationResult } from 'express-validator';
const validateCreateUserRequest = [
body('email').isEmail().normalizeEmail(),
body('password').isLength({ min: 8 }),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
next();
}
];
export default validateCreateUserRequest;
middleware นี้ตรวจสอบความถูกต้องของฟิลด์ email และ password ในเนื้อหาคำขอ หากการตรวจสอบความถูกต้องล้มเหลว จะส่งคืนการตอบสนอง 400 Bad Request พร้อมอาร์เรย์ของข้อความแสดงข้อผิดพลาด คุณสามารถใช้ middleware นี้ในตัวจัดการเส้นทางของคุณได้ดังนี้:
import express from 'express';
import validateCreateUserRequest from './middleware/validateCreateUserRequest';
const app = express();
const port = 3000;
app.post('/users', validateCreateUserRequest, (req, res) => {
// If validation passes, create the user
res.send('User created successfully!');
});
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
6. Dependency Injection for Middleware
เมื่อฟังก์ชัน middleware ของคุณขึ้นอยู่กับบริการภายนอกหรือการกำหนดค่า การฉีดการพึ่งพาอาศัยกันสามารถช่วยปรับปรุงความสามารถในการทดสอบและความสามารถในการบำรุงรักษาได้ คุณสามารถใช้คอนเทนเนอร์ dependency injection เช่น tsyringe หรือเพียงแค่ส่งผ่านการอ้างอิงเป็นอาร์กิวเมนต์ไปยัง middleware factories ของคุณ
นี่คือตัวอย่างโดยใช้ middleware factory พร้อม dependency injection:
// src/services/UserService.ts
export class UserService {
async createUser(email: string, password: string): Promise {
// In a real application, you would save the user to a database
console.log(`Creating user with email: ${email} and password: ${password}`);
await new Promise(resolve => setTimeout(resolve, 500)); // Simulate a database operation
}
}
// src/middleware/createUserMiddleware.ts
import { Request, Response, NextFunction } from 'express';
import { UserService } from '../services/UserService';
function createCreateUserMiddleware(userService: UserService) {
return async (req: Request, res: Response, next: NextFunction) => {
try {
const { email, password } = req.body;
await userService.createUser(email, password);
res.status(201).send('User created successfully!');
} catch (error) {
next(error);
}
};
}
export default createCreateUserMiddleware;
// src/app.ts
import express from 'express';
import createCreateUserMiddleware from './middleware/createUserMiddleware';
import { UserService } from './services/UserService';
import errorHandler from './middleware/errorHandler';
const app = express();
const port = 3000;
app.use(express.json()); // Parse JSON request bodies
const userService = new UserService();
const createUserMiddleware = createCreateUserMiddleware(userService);
app.post('/users', createUserMiddleware);
app.use(errorHandler);
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
แนวทางปฏิบัติที่ดีที่สุดสำหรับ TypeScript Middleware
- ทำให้ฟังก์ชัน middleware มีขนาดเล็กและเน้น ฟังก์ชัน middleware แต่ละฟังก์ชันควรมีความรับผิดชอบเพียงอย่างเดียว
- ใช้ชื่อที่สื่อความหมายสำหรับฟังก์ชัน middleware ของคุณ ชื่อควรระบุอย่างชัดเจนว่า middleware ทำอะไร
- จัดการข้อผิดพลาดอย่างถูกต้อง จับข้อผิดพลาดเสมอและส่งต่อไปยัง error handling middleware โดยใช้
next(error) - ใช้ custom request/response types เพื่อเพิ่มความปลอดภัยของชนิดข้อมูล เพิ่มส่วนต่อประสาน
RequestและResponseด้วยคุณสมบัติที่กำหนดเองตามต้องการ - ใช้ middleware factories เพื่อกำหนดค่า middleware ด้วยตัวเลือกเฉพาะ
- เอกสารฟังก์ชัน middleware ของคุณ อธิบายว่า middleware ทำอะไรและควรใช้งานอย่างไร
- ทดสอบฟังก์ชัน middleware ของคุณอย่างละเอียด เขียน unit tests เพื่อให้แน่ใจว่าฟังก์ชัน middleware ของคุณทำงานอย่างถูกต้อง
บทสรุป
TypeScript ช่วยเพิ่มประสิทธิภาพในการพัฒนา Express middleware อย่างมากโดยการเพิ่มการพิมพ์แบบคงที่ ปรับปรุงความสามารถในการบำรุงรักษาโค้ด และตรวจจับข้อผิดพลาดตั้งแต่เนิ่นๆ ด้วยการเรียนรู้รูปแบบประเภทขั้นสูง เช่น custom request/response types, middleware factories, asynchronous middleware, error handling middleware และ request validation middleware คุณสามารถสร้างแอปพลิเคชัน Express ที่แข็งแกร่ง ปรับขนาดได้ และปลอดภัยต่อชนิดข้อมูลได้ โปรดจำไว้ว่าให้ปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุดเพื่อให้ฟังก์ชัน middleware ของคุณมีขนาดเล็ก เน้น และมีเอกสารที่ดี